package mmi.gui;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.EventListener;
import java.util.ListIterator;
import java.util.Vector;

import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.event.MouseInputListener;
import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction;

import mmi.event.ColourChangedEvent;
import mmi.event.ColourChangedEventListener;
import mmi.event.ModeChangedEvent;
import mmi.event.ModeChangedEventListener;
import mmi.event.ObjectAddedEvent;
import mmi.event.ObjectAddedEventListener;
import mmi.event.ObjectMovedEvent;
import mmi.event.ObjectMovedEventListener;
import mmi.event.ObjectResizedEvent;
import mmi.event.ObjectResizedEventListener;
import mmi.event.ObjectRotatedEvent;
import mmi.event.ObjectRotatedEventListener;
import mmi.event.ShapeChangedEvent;
import mmi.event.ShapeChangedEventListener;
import mmi.gui.shape.FreeLine;
import mmi.gui.shape.Shape;
import mmi.gui.shape.TextObject;
import mmi.gui.shape.Triangle;
import mmi.gui.shape.Shape.Shapes;
import mmi.gui.widget.StringAsker;
import mmi.utils.Action;

/**
 * DrawingArea is a class which provides some functions and functionality to create and for creating
 * a drawing area. This drawing area keeps track of actions
 * 
 * @author louis
 *
 */
public class DrawingArea extends JPanel implements MouseInputListener, EventListener
{
	/*
	 * Several modes are supported. Modes can be both program modes, i.e.: Draw, Move, Resize and Delete
	 * but they can also represent internal modes, as Dragging and Drawing.
	 * 
	 * Modes are used to keep track of states in the program. That's why also the current mode and the
	 * return mode are being kept track of.
	 */
	public static enum Modes {None, Draw, Drawing, DrawingTriangle, Move, Dragging, Resize, Resizing, Delete, Fill, Top, Text, Rotate, Rotating}
	private static final long serialVersionUID = 1703289939600899665L;

	private MainFrame parent = null;

	/*
	 * New objects have this shape
	 */
	private Shape.Shapes currentDrawingShape = Shape.Shapes.Rectangle;;
	private Modes currentMode = Modes.Draw;
	private Modes returnMode = Modes.None;

	/*
	 * Objects can have certain colours. This members indicate the colours of new objects.
	 * Also they can be used to reassign colours to objects. Line width is the width of the
	 * line around (and outside) of the object.
	 * 
	 * Line width is not the same as font size. Both members can be used for both properties,
	 * but it seems more hygienic to seperate them
	 */
	private Color fillColour = Color.white;
	private Color lineColour = Color.black;
	private int lineWidth = 2;
	private int fontSize = 40;
	private Font font = new Font("Times New Roman", Font.PLAIN, fontSize);

	/*
	 * As for the internal administration, we also keep track of when a mouse is pressed. Not every mouse
	 * press will end up here, only if the mouse press is of consequence for another state, such as the begin
	 * position of dragging a shape.
	 * 
	 * The relativeMousePressed is to keep track where the user pressed the mouse relative to the top left
	 * corner of the shape.
	 */
	private Point2D mousePressed = new Point();
	private Point2D mouseReleased = new Point();
	private Point2D relativeMousePressed = new Point();

	private Timestamp mousePressedTS = new Timestamp(0);
	private Timestamp mouseReleasedTS = new Timestamp(0);

	/*
	 * If you create a triangle, when the two points for the base are already fixed, one point is still variable.
	 * It is neither the point where you pressed the mouse or released the mouse, but the projection point of the
	 * object while moving the mouse.
	 * 
	 * Set to null if it's not used. In the proces of creating triangles you might want to test for its existance.
	 */
	private Point2D projectedObjectPoint = null;

	/*
	 * Sometimes you might need to keep track of a shape that's not yet added to the displayList, but yet you
	 * would like to have it drawn: use this currentlyDrawingObject.
	 * 
	 * If you don't want to use it, set it to null! Otherwise it will be drawn.
	 */
	private Shape currentlyDrawingObject = null;

	/*
	 * The actual drawing sheet
	 */
	private JPanel drawingCanvas;

	/*
	 * DisplayList is a list of shapes currently on the screen (to be displayed).
	 * 
	 * ActionList is a list of actions to keep track of adding, moving, resizing, &c. of the shapes. This
	 * actionList is also used to save an internal representation to a file, and to load a replay.
	 */
	private Vector<Shape> displayList = new Vector<Shape>();
	private Vector<Action> actionList = new Vector<Action>();
	private Vector<Action> ActionAddList = new Vector<Action>();
	private Vector<Action> ActionMoveList = new Vector<Action>();
	private Vector<Action> ActionResizeList = new Vector<Action>();
	private Vector<Action> ActionRotateList = new Vector<Action>();
	//	private Vector<Action> undoList = new Vector<Action>();
	//	private Vector<Action> redoList = new Vector<Action>();

	/*
	 * Keep track of the side which you are resizing.
	 */
	private Shape.Sides resizeSide = null;

	/*
	 * This timer should only be used to replay actions. The only function to start and stop it is
	 * startTimer
	 * @see startTimer
	 */
	private Timer replayTimer;

	/*
	 * These members are not local because otherwise the anonymous functions cannot access it
	 */
	double percentage = 0;

	double currentTime = 0;

	double timeFrame = 0;

	int replayPointer = 0;

	Timestamp firstTS = null;
	Timestamp lastTS = null;
	/*
	 * in milliseconds
	 * - A lower speed means the replay will take longer than the original
	 * - replay speed of 1000, means the replay will take as long as the recording
	 * - replay speed of >1000 means the replay will be shorter than the recording
	 */
	private int replaySpeed = 50;
	/*
	 * We use events to implements the observer design pattern. This list is to contain all these
	 * event listeners
	 */
	protected javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList();
	/**
	 * Default constructor
	 */
	public DrawingArea(MainFrame parent) {
		this.parent = parent;
		Application.Trace("+++ New drawing area");
		reset();

		drawingCanvas = new JPanel();
		setBackground(Color.WHITE);
		repaint();

		setShape(Shapes.Rectangle);
		setDraw();

		addMouseListener(this);
		addMouseMotionListener(this);
		repaint();
	}
	private void addAction(Action action)
	{
		actionList.add(action);

		switch(action.getAction())
		{
		case AddObject:
			ActionAddList.add(action);
			fireObjectAddedEvent(new ObjectAddedEvent(this));
			break;
		case MoveObject:
			ActionMoveList.add(action);
			fireObjectMovedEvent(new ObjectMovedEvent(this));
			break;
		case ResizeObject:
			ActionResizeList.add(action);
			fireObjectResizedEvent(new ObjectResizedEvent(this));
			break;
		case RotateObject:
			ActionRotateList.add(action);
			fireObjectRotatedEvent(new ObjectRotatedEvent(this));
			break;
		default:
			return;
		}
	}

	public void addColourChangedEventListener(ColourChangedEventListener listener)
	{
		listenerList.add(ColourChangedEventListener.class, listener);
	}

	public void addModeChangedEventListener(ModeChangedEventListener listener)
	{
		listenerList.add(ModeChangedEventListener.class, listener);
	}

	public void addObjectAddedEventListener(ObjectAddedEventListener listener)
	{
		listenerList.add(ObjectAddedEventListener.class, listener);
	}

	public void addObjectMovedEventListener(ObjectMovedEventListener listener)
	{
		listenerList.add(ObjectMovedEventListener.class, listener);
	}

	public void addObjectResizedEventListener(ObjectResizedEventListener listener)
	{
		listenerList.add(ObjectResizedEventListener.class, listener);
	}

	public void addObjectRotatedEventListener(ObjectRotatedEventListener listener)
	{
		listenerList.add(ObjectRotatedEventListener.class, listener);
	}

	/**
	 * @param shape add shape to displayList
	 */
	public void addShape(Shape shape) {
		if(shape != null) {
			displayList.add(shape);
		}
	}

	public void addShapeChangedEventListener(ShapeChangedEventListener listener)
	{
		listenerList.add(ShapeChangedEventListener.class, listener);
	}

	/**
	 * Creates an object with shape shape (member variable), with the size of the dragged area.
	 * This is an recorded action.
	 * @param position the x and y coordinate
	 */
	private void createObjectOn(Point2D position)
	{
		Shape newShape = null;

		newShape = Shape.createShape(currentlyDrawingObject,
				currentDrawingShape,
				new Point2D.Double(mousePressed.getX(), mousePressed.getY()),
				position,
				lineColour, fillColour, lineWidth);
		addAction(new Action(Action.Actions.AddObject, newShape, mousePressedTS, mouseReleasedTS));
		addShape(newShape);
		currentlyDrawingObject = null;

		repaint();

		//		actionList.add(new Action(Action.Actions.AddObject, newShape, mousePressedTS, mouseReleasedTS));
	}



	/**
	 * Creates an object based on shape
	 * @param shape original shape
	 * @param coordinate place of the newly created shape
	 */
	private void createObjectOn(Shape shape, Point2D coordinate)
	{
		double xdif = shape.getStart().getX() - coordinate.getX();
		double ydif = shape.getStart().getY() - coordinate.getY();
		Shape newShape = Shape.createShape(shape,
				shape.getShape(),
				new Point2D.Double(shape.getStart().getX() + xdif, shape.getStart().getY() + ydif),
				new Point2D.Double(shape.getEnd().getX() + xdif  , shape.getEnd().getY() + ydif),
				shape.getLineColour(), shape.getFillColour(), shape.getLineWidth());
		newShape.setMiddle(new Point2D.Double(shape.getMiddle().getX() + xdif, shape.getMiddle().getY() + ydif));

		addShape(newShape);
		currentlyDrawingObject = null;
	}

	/**
	 * If there are multiple shapes in the position (x,y) the one with the highest Z-index
	 * will be removed. In this way you cannot accidently removed hidden shapes.
	 * @param coordinate the x and y coordinate of the mouse press
	 */
	private void deleteLastObject(Point2D coordinate)
	{
		Shape curShape = null;

		ListIterator<Shape> litr = displayList.listIterator(displayList.size());
		while(litr.hasPrevious())
		{
			curShape = litr.previous();
			if(curShape != null && curShape.contains(coordinate))
			{
				displayList.remove(curShape);
				Application.Trace("+++ Deleting object: " + curShape.toString());
				addAction(new Action(Action.Actions.DeleteObject, curShape, mousePressedTS, mouseReleasedTS));
				//				actionList.add(new Action(Action.Actions.DeleteObject, curShape, mousePressedTS, mouseReleasedTS));
				return;
			}
		}
	}

	/**
	 * If there are multiple shapes in the position (x,y) the one with the highest Z-index
	 * will be filled. In this way you cannot accidently fill hidden shapes.
	 * @param coordinate the x and y coordinate of the mouse press
	 */
	private void fillLastObject(Point2D coordinate, Shape.ShapePart shapePart)
	{
		Shape curShape = null;

		ListIterator<Shape> litr = displayList.listIterator(displayList.size());
		while(litr.hasPrevious())
		{
			curShape = litr.previous();
			if(curShape != null && curShape.contains(coordinate))
			{
				if(shapePart.equals(Shape.ShapePart.Body))
				{
                                        if (curShape instanceof mmi.gui.shape.Line){
                                            curShape.setLineWidth(lineWidth);
                                            curShape.setLineColour(lineColour);
                                        }
					curShape.setFillColour(fillColour);
					addAction(new Action(Action.Actions.FillObject, curShape, mousePressedTS, mouseReleasedTS));
					//					actionList.add(new Action(Action.Actions.FillObject, curShape, mousePressedTS, mouseReleasedTS));
				} else
				{
					Application.Trace("bla");
                                        curShape.setLineWidth(lineWidth);
					curShape.setLineColour(lineColour);
					addAction(new Action(Action.Actions.FillContourObject, curShape, mousePressedTS, mouseReleasedTS));
					//					actionList.add(new Action(Action.Actions.FillContourObject, curShape, mousePressedTS, mouseReleasedTS));
				}
				Application.Trace("+++ Filling object: " + curShape.toString());

				return;
			}
		}
	}

	void fireColourChangedEvent(ColourChangedEvent evt)
	{
		Object[] listeners  = listenerList.getListenerList();

		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for(int i=0; i < listeners.length; i+=2)
		{
			if(listeners[i] == ColourChangedEventListener.class)
			{
				((ColourChangedEventListener)listeners[i+1]).colourChangedEventOccured(evt);
			}
		}
	}

	void fireModeChangedEvent(ModeChangedEvent evt)
	{
		Object[] listeners = listenerList.getListenerList();

		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for(int i=0; i < listeners.length; i+=2)
		{
			if(listeners[i] == ModeChangedEventListener.class)
			{
				((ModeChangedEventListener)listeners[i+1]).modeChangedEventOccured(evt);
			}
		}
	}

	void fireObjectAddedEvent(ObjectAddedEvent evt)
	{
		Object[] listeners = listenerList.getListenerList();

		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for(int i=0; i < listeners.length; i+=2)
		{
			if(listeners[i] == ObjectAddedEventListener.class)
			{
				((ObjectAddedEventListener)listeners[i+1]).objectAddedEventOccured(evt);
			}
		}

	}

	void fireObjectMovedEvent(ObjectMovedEvent evt)
	{
		Object[] listeners = listenerList.getListenerList();

		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for(int i=0; i < listeners.length; i+=2)
		{
			if(listeners[i] == ObjectMovedEventListener.class)
			{
				((ObjectMovedEventListener)listeners[i+1]).objectMovedEventOccured(evt);
			}
		}

	}

	void fireObjectResizedEvent(ObjectResizedEvent evt)
	{
		Object[] listeners = listenerList.getListenerList();

		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for(int i=0; i < listeners.length; i+=2)
		{
			if(listeners[i] == ObjectResizedEventListener.class)
			{
				((ObjectResizedEventListener)listeners[i+1]).objectResizedEventOccured(evt);
			}
		}

	}

	void fireObjectRotatedEvent(ObjectRotatedEvent evt)
	{
		Object[] listeners = listenerList.getListenerList();

		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for(int i=0; i < listeners.length; i+=2)
		{
			if(listeners[i] == ObjectRotatedEventListener.class)
			{
				((ObjectRotatedEventListener)listeners[i+1]).objectRotatedEventOccured(evt);
			}
		}

	}

	void fireShapeChangedEvent(ShapeChangedEvent evt)
	{
		Object[] listeners = listenerList.getListenerList();

		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for(int i=0; i < listeners.length; i+=2)
		{
			if(listeners[i] == ShapeChangedEventListener.class)
			{
				((ShapeChangedEventListener)listeners[i+1]).shapeChangedEventOccured(evt);
			}
		}
	}

	/**
	 * @return the actionAddList
	 */
	public Vector<Action> getActionAddList() {
		return ActionAddList;
	}

	/**
	 * @return the actionList
	 */
	public Vector<Action> getActionList() {
		return actionList;
	}

	/**
	 * @return the actionMoveList
	 */
	public Vector<Action> getActionMoveList() {
		return ActionMoveList;
	}

	/**
	 * @return the actionResizeList
	 */
	public Vector<Action> getActionResizeList() {
		return ActionResizeList;
	}

	/**
	 * @return the actionRotateList
	 */
	public Vector<Action> getActionRotateList() {
		return ActionRotateList;
	}

	public Modes getCurrentMode() {
		return currentMode;
	}

	/**
	 * @return the displayList
	 */
	public Vector<Shape> getDisplayList() {
		return displayList;
	}

	/**
	 * @return the drawingCanvas
	 */
	public JPanel getDrawingCanvas() {
		return drawingCanvas;
	}

	/**
	 * @return the fillColour
	 */
	public Color getFillColour() {
		return fillColour;
	}

	/**
	 * This function determines the timestamp of the first performed action
	 * @return timestamp of the first (from a time perspective) action in action list
	 */
	private Timestamp getFirstTimeStamp()
	{
		Timestamp ts = new Timestamp(actionList.get(0).getTimeEnd().getTime());

		for(Action a : actionList)
		{
			if(a.getTimeEnd().getTime() < ts.getTime()) {
				ts = a.getTimeEnd();
			}
		}

		return ts;
	}

	/**
	 * This function determines the timestamp of the last performed action
	 * @return timestamp of the last (from a time perspective) action in action list
	 */
	private Timestamp getLastTimeStamp()
	{
		Timestamp ts = new Timestamp(actionList.get(0).getTimeEnd().getTime());

		for(Action a : actionList)
		{
			if(a.getTimeEnd().getTime() > ts.getTime()) {
				ts = a.getTimeEnd();
			}
		}

		return ts;
	}

	/**
	 * @return the lineColour
	 */
	public Color getLineColour() {
		return lineColour;
	}

	/**
	 * @return the lineWidth
	 */
	public int getLineWidth() {
		return lineWidth;
	}
	public Modes getPreviousMode() {
		return returnMode;
	}
	/**
	 * @return the shape
	 */
	public Shapes getShape() {
		return currentDrawingShape;
	}
	private void handleDeleteModeMousePressed(Point2D position)
	{
		deleteLastObject(position);
		repaint();
	}
	private void handleDraggingModeMouseDragged(Point2D position)
	{
		if(isFreeLineShape())
		{
			if(currentlyDrawingObject.getClass().equals(FreeLine.class))
			{
				FreeLine fl = (FreeLine) currentlyDrawingObject;
				fl.addPoint(position);
				repaint();
			}
		} else
		{
			showDraggingObject(position);
		}
	}

	private void handleDraggingModeMouseReleased(Point2D position)
	{
		if(currentlyDrawingObject == null) {
			Application.Trace(" >  No object made with this press/release combination");
		} else {
			Application.Trace(" >  Object created: " + currentlyDrawingObject.toString() );
		}

		if(isFreeLineShape())
		{
			displayList.add(currentlyDrawingObject);
			addAction(new Action(Action.Actions.AddObject, currentlyDrawingObject, mousePressedTS, mouseReleasedTS));
			//			actionList.add(new Action(Action.Actions.AddObject, currentlyDrawingObject, mousePressedTS, mouseReleasedTS));
		} else
		{
			/*
			 * If there hasn't been dragged in dragging mode, currentlyDrawingObject
			 * will be null, and if we add null to displayList,...
			 */
			if(currentlyDrawingObject != null) {
				createObjectOn(position);
			}
		}
		setDraw();

	}


	private void handleDrawModeMouseDragged(Point2D position)
	{
		if(isTriangleShape())
		{
			currentDrawingShape = Shape.Shapes.Line;
			showDraggingObject(position);
			currentDrawingShape = Shape.Shapes.Triangle;
		}
	}
	private void handleDrawModeMousePressed(Point2D position)
	{
		if(isFreeLineShape())
		{
			returnMode = currentMode;
			setDragging();
			currentlyDrawingObject = new FreeLine(position, position, 2);
			currentlyDrawingObject.setLineColour(lineColour);
			currentlyDrawingObject.setFillColour(fillColour);
			currentlyDrawingObject.setLineWidth(lineWidth);
		} else if(isTriangleShape())
		{
			mousePressed = position;
		} else
		{
			setDragging();

			mousePressed = position;
		}
		repaint();
	}
	private void handleDrawModeMouseReleased(Point2D position)
	{
		if(isTriangleShape() && isDrawMode())
		{
			mouseReleased = position;
			currentlyDrawingObject = null;
			setDrawingTriangle();
		} else if(isTriangleShape() && isDrawingTriangleMode())
		{
			setDraw();
		}
	}


	private void handleFillModeMousePressed(Point2D position, int mouseButton)
	{
		if(mouseButton == MouseEvent.BUTTON3)
		{
			fillLastObject(position, Shape.ShapePart.Border);
		} else if(mouseButton == MouseEvent.BUTTON1)
		{
			fillLastObject(position, Shape.ShapePart.Body);
		}
		repaint();
	}
	private void handleFillModeMouseReleased(Point2D position)
	{
		repaint();
	}


	private void handleMoveModeMouseDragged(Point2D position)
	{
		if(currentlyDrawingObject != null)
		{
			showMovingObject(new Point2D.Double(position.getX() - relativeMousePressed.getX(), position.getY() - relativeMousePressed.getY()));
			repaint();
		}
	}
	private void handleMoveModeMousePressed(Point2D position)
	{
		moveLastObject(position);
		repaint();

		if(currentlyDrawingObject != null)
		{
			if(currentlyDrawingObject instanceof TextObject)
			{
				setResize();
				handleResizeModeMousePressed(position);
			} else
			{
			mousePressed = position;
			relativeMousePressed = new Point2D.Double(position.getX() - currentlyDrawingObject.getStart().getX(), position.getY() - currentlyDrawingObject.getStart().getY());
			}
		}
	}
	private void handleMoveModeMouseReleased(Point2D position)
	{
		if(currentlyDrawingObject != null)
		{
			addAction(new Action(Action.Actions.MoveObject, currentlyDrawingObject, mousePressedTS, mouseReleasedTS));
			//			actionList.add(new Action(Action.Actions.MoveObject, currentlyDrawingObject, mousePressedTS, mouseReleasedTS));
			createObjectOn(currentlyDrawingObject, new Point2D.Double(position.getX() - relativeMousePressed.getX(), position.getY() - relativeMousePressed.getY()));
			fireObjectMovedEvent(new ObjectMovedEvent(this));
		}
	}


	private void handleResizeModeMouseDragged(Point2D position)
	{
		if(currentlyDrawingObject != null)
		{
			Shape.Sides side = currentlyDrawingObject.isSide(position);
			if(resizeSide == null)
			{
				resizeSide = side;
				Application.Trace("Resize side: " + resizeSide);
			}
			if(side != null)
			{
				showResizingObject(side, position);
				mousePressed = currentlyDrawingObject.getStart();
			}

			repaint();
		}

		currentMode = Modes.Resizing;
	}
	private void handleResizeModeMousePressed(Point2D position)
	{
		resizeLastObject(position);

		if(currentlyDrawingObject != null)
		{
			mousePressed = position;
			currentlyDrawingObject.setShowBoundingBox(true);
			currentlyDrawingObject.setShowBoundingLines(true);
			repaint();
			relativeMousePressed = new Point2D.Double(position.getX() - currentlyDrawingObject.getStart().getX(), position.getY() - currentlyDrawingObject.getStart().getY());

			Application.Trace("Object found to be resized");
		}


	}
	private void handleResizeModeMouseReleased(Point2D position)
	{
		if(currentlyDrawingObject != null)
		{
			//			addAction(new Action(Action.Actions.ResizeObject, currentlyDrawingObject, mousePressedTS, mouseReleasedTS));
			//			actionList.add(new Action(Action.Actions.ResizeObject, currentlyDrawingObject, mousePressedTS, mouseReleasedTS));
			createObjectOn(currentlyDrawingObject, new Point2D.Double(position.getX() - relativeMousePressed.getX(), position.getY() - relativeMousePressed.getY()));
		}
	}


	private void handleResizingModeMouseDragged(Point2D position)
	{
		if(currentlyDrawingObject != null)
		{
			showResizingObject(resizeSide, position);
			repaint();
		}
	}

	private void handleResizingModeMouseReleased(Point2D position)
	{
		if (currentlyDrawingObject != null) 
		{
			currentMode = Modes.Resize;
			currentlyDrawingObject.setShowBoundingBox(false);
			repaint();
			addAction(new Action(Action.Actions.ResizeObject,
					currentlyDrawingObject, mousePressedTS, mouseReleasedTS));
			addShape(currentlyDrawingObject);
			// displayList.add(currentlyDrawingObject);
			currentlyDrawingObject = null;
			resizeSide = null;
		}
	}

        private Path2D rotatedShape = null;
        private Shape shape = null;
        private double pressTheta;
        private int shapesIndex = 0;

	private void handleRotateModeMouseDragged(Point2D position)
	{
		if(currentlyDrawingObject != null)
		{
			Shape.Sides side = currentlyDrawingObject.isSide(position);
			if(resizeSide == null)
			{
				resizeSide = side;
				Application.Trace("Rotate side: " + resizeSide);
			}
			if(side != null)
			{
				showResizingObject(side, position);
				mousePressed = currentlyDrawingObject.getStart();
			}

			repaint();
		}

		currentMode = Modes.Rotating;
	}
	private void handleRotateModeMousePressed(Point2D position)
	{
            rotateLastObject(position);
		for (int i = displayList.size() - 1; i >= 0; i--) {
                    if (displayList.get(i).contains(position) && displayList.get(i) instanceof mmi.gui.shape.Line ) {
                        shapesIndex = i;
                        shape = displayList.get(i);
                        rotatedShape = new Path2D.Double(new Line2D.Double(shape.getStart(), shape.getEnd()));

                        displayList.remove(i);
                        displayList.add(i, new mmi.gui.shape.Line(shape.getStart(), shape.getEnd(), shape.getLineColour(), shape.getFillColour(), shape.getLineWidth()));
                        repaint();

                        pressTheta = Math.atan2(position.getY() - rotatedShape.getBounds2D().getCenterY(), position.getX() - rotatedShape.getBounds2D().getCenterX());
                        currentMode = Modes.Rotating;
                        Application.Trace("Object found to be rotated");
                        return;
                    }
                }
                rotatedShape = null;
                repaint();
                
	}
	private void handleRotateModeMouseReleased(Point2D position)
	{
                //rotatedShape = null;
                //shape = null;
                //repaint();
		//currentMode = Modes.Rotate;
	}


	private void handleRotatingModeMouseDragged(Point2D position)
	{
		if ( rotatedShape != null ) {

                        double dragTheta = Math.atan2(position.getY() - rotatedShape.getBounds2D().getCenterY(), position.getX() - rotatedShape.getBounds2D().getCenterX());
                        double deltaTheta = dragTheta - pressTheta;

                        rotatedShape = new Path2D.Double(new Line2D.Double(shape.getStart(), shape.getEnd()));

                        displayList.remove(shapesIndex);
                        AffineTransform rotateTransform = AffineTransform.getRotateInstance(deltaTheta, rotatedShape.getBounds2D().getCenterX(), rotatedShape.getBounds2D().getCenterY());
                        rotatedShape.transform(rotateTransform);

                        if ( (dragTheta >= 0 && dragTheta <= Math.PI/2) || (dragTheta <= -Math.PI/2 && dragTheta >= -Math.PI) ) {
                            displayList.add(shapesIndex, new mmi.gui.shape.Line(new Point2D.Double(rotatedShape.getBounds2D().getX(),rotatedShape.getBounds2D().getY()), new Point2D.Double(rotatedShape.getBounds2D().getMaxX(),rotatedShape.getBounds2D().getMaxY()), shape.getLineColour(), shape.getFillColour(), shape.getLineWidth()));
                        }
                        else {
                            displayList.add(shapesIndex, new mmi.gui.shape.Line(new Point2D.Double(rotatedShape.getBounds2D().getX(),rotatedShape.getBounds2D().getMaxY()), new Point2D.Double(rotatedShape.getBounds2D().getMaxX(),rotatedShape.getBounds2D().getY()), shape.getLineColour(), shape.getFillColour(), shape.getLineWidth()));
                        }
                        repaint();
            }
	}

        private void handleRotatingModeMouseReleased(Point2D position)
	{
                addAction(new Action(Action.Actions.RotateObject, currentlyDrawingObject, mousePressedTS, mouseReleasedTS));
                currentMode = Modes.Rotate;
                rotatedShape = null;
                shape = null;
                repaint();
	}

	private void handleTextModeMousePressed(Point2D position)
	{
		StringAsker sa = new StringAsker(parent, true);
		String text = sa.getValue();

		if(text != null)
		{
			Shape shape = new TextObject(position, text, font, lineColour);

			addAction(new Action(Action.Actions.AddObject, shape, mousePressedTS, mouseReleasedTS));
			addShape(shape);

			//			actionList.add(new Action(Action.Actions.AddObject, shape, mousePressedTS, mouseReleasedTS));
		}
		repaint();
	}

	private void handleTopModeMousePressed(Point2D position)
	{
		topLastObject(position);
		repaint();
	}

	public boolean isDeleteMode()
	{
		return currentMode.equals(Modes.Delete);
	}
	public boolean isDraggingMode()
	{
		return currentMode.equals(Modes.Dragging);
	}
	public boolean isDrawingMode()
	{
		return currentMode.equals(Modes.Drawing);
	}
	public boolean isDrawingTriangleMode()
	{
		return currentMode.equals(Modes.DrawingTriangle);
	}
	public boolean isDrawMode()
	{
		return currentMode.equals(Modes.Draw);
	}
	public boolean isFillMode()
	{
		return currentMode.equals(Modes.Fill);
	}
	public boolean isFreeLineShape()
	{
		return currentDrawingShape.equals(Shapes.FreeLine);
	}
	public boolean isMoveMode()
	{
		return currentMode.equals(Modes.Move);
	}
	public boolean isResizeMode()
	{
		return currentMode.equals(Modes.Resize);
	}
	public boolean isResizingMode()
	{
		return currentMode.equals(Modes.Resizing);
	}
	public boolean isRotateMode()
	{
		return currentMode.equals(Modes.Rotate);
	}
	public boolean isRotatingMode()
	{
		return currentMode.equals(Modes.Rotating);
	}
	public boolean isTextMode()
	{
		return currentMode.equals(Modes.Text);
	}
	public boolean isTopMode()
	{
		return currentMode.equals(Modes.Top);
	}
	public boolean isTriangleShape()
	{
		return currentDrawingShape.equals(Shapes.Triangle);
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		Application.Trace("+++ MouseClicked at [x=" + e.getX() + ",y=" + e.getY() + "] in " + currentMode + " state");
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		// Application.Trace("+++ MouseDragged at [x=" + e.getX() + ",y=" + e.getY() + "]");
		parent.cursorDragged(mousePressed, e.getPoint());

		switch (currentMode) {
		case Draw:
			handleDrawModeMouseDragged(e.getPoint());
			break;
		case Dragging:
			handleDraggingModeMouseDragged(e.getPoint());
			break;
		case Move:
			handleMoveModeMouseDragged(e.getPoint());
			break;
		case Resize:
			handleResizeModeMouseDragged(e.getPoint());
			break;
		case Resizing:
			handleResizingModeMouseDragged(e.getPoint());
			break;
		case Rotate:
			handleRotateModeMouseDragged(e.getPoint());
			break;
		case Rotating:
			handleRotatingModeMouseDragged(e.getPoint());
			break;
		default:
			break;
		}

	}
	@Override
	public void mouseEntered(MouseEvent e) {
		//		System.out.println("mouseEntered");
	}
	@Override
	public void mouseExited(MouseEvent e) {
		//		System.out.println("mouseExited");
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		parent.cursorMoved(e.getPoint());

		if(isDrawingTriangleMode())
		{
			currentlyDrawingObject = new Triangle(mousePressed, mouseReleased, e.getPoint(), lineColour, fillColour, lineWidth);
			repaint();
		}
	}
	@Override
	public void mousePressed(MouseEvent e) {
		Application.Trace("+++ MousePressed at [x=" + e.getX() + ",y=" + e.getY() + "] in " + currentMode + " state");
		mousePressedTS = new java.sql.Timestamp(Calendar.getInstance().getTime().getTime());

		switch (currentMode) {
		case Draw:
			handleDrawModeMousePressed(e.getPoint());
			break;
		case DrawingTriangle:
			addAction(new Action(Action.Actions.AddObject, currentlyDrawingObject, mousePressedTS, mouseReleasedTS));
			addShape(currentlyDrawingObject);

			//			actionList.add(new Action(Action.Actions.AddObject, currentlyDrawingObject, mousePressedTS, mouseReleasedTS));
			currentlyDrawingObject = null;
			break;
		case Fill:
			handleFillModeMousePressed(e.getPoint(), e.getButton());
			break;
		case Top:
			handleTopModeMousePressed(e.getPoint());
			break;
		case Delete:
			handleDeleteModeMousePressed(e.getPoint());
			break;
		case Move:
			handleMoveModeMousePressed(e.getPoint());
			break;
		case Resize:
			handleResizeModeMousePressed(e.getPoint());
			break;
		case Rotate:
			handleRotateModeMousePressed(e.getPoint());
			break;
		case Text:
			handleTextModeMousePressed(e.getPoint());
			break;
		default:
			break;
		}
	}
	@Override
	public void mouseReleased(MouseEvent e) {
		Application.Trace("+++ MouseReleased at [x=" + e.getX() + ",y=" + e.getY() + "] in " + currentMode + " state");
		parent.cursorReleased();
		mouseReleasedTS = new java.sql.Timestamp(Calendar.getInstance().getTime().getTime());

		projectedObjectPoint = null;

		switch (currentMode) {
		case Draw:
			handleDrawModeMouseReleased(e.getPoint());
			break;
		case DrawingTriangle:
			setDraw();
			break;
		case Dragging:
			handleDraggingModeMouseReleased(e.getPoint());
			break;
		case Fill:
			handleFillModeMouseReleased(e.getPoint());
			break;
		case Move:
			handleMoveModeMouseReleased(e.getPoint());
			break;
		case Resize:
			handleResizeModeMouseReleased(e.getPoint());
			break;
		case Resizing:
			handleResizingModeMouseReleased(e.getPoint());
			break;
		case Rotate:
			handleRotateModeMouseReleased(e.getPoint());
			break;
		case Rotating:
			handleRotatingModeMouseReleased(e.getPoint());
			break;
		default:
			break;
		}
	}

	/**
	 * If there are multiple shapes in the position (x,y) the one with the highest Z-index
	 * will be removed. In this way you cannot accidently removed move shapes. The shape
	 * removed is added to currentlyDrawingObject, such that it won't get lost.
	 * @param coordinate the x and y coordinate of the mouse press
	 */
	private void moveLastObject(Point2D coordinate)
	{
		Shape curShape = null;
		currentlyDrawingObject = null;

		ListIterator<Shape> litr = displayList.listIterator(displayList.size());
		while(litr.hasPrevious())
		{
			curShape = litr.previous();
			if(curShape != null && curShape.contains(coordinate))
			{
				displayList.remove(curShape);
				Application.Trace("+++ Moving object: " + curShape.toString());
				currentlyDrawingObject = curShape;
				return;
			}
		}

		/*
		 * Apparently a proper shape hasn't been found. That means we have nothing to move
		 */
		if(litr.previousIndex() == -1) {
			currentlyDrawingObject = null;
		}
	}
	/*
	 * (non-Javadoc)
	 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
	 */
	@Override
	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		paintGraphics2D((Graphics2D) g);
	}
	/**
	 * This function displays all the objects from displayList on the screen. It will also draw the
	 * temporary object currenlyDrawingObject!
	 * @param g2d the device being drawn to
	 */
	private void paintGraphics2D(Graphics2D g2d)
	{
		for(Shape s : displayList)
		{
			if(s != null) {
				s.draw(g2d);
			}
		}

		if(currentlyDrawingObject != null)
		{
			currentlyDrawingObject.draw(g2d);
		}
	}

	/**
	 * Print the current output of the action list to some output
	 */
	public void printActionList()
	{
		for(Action a : actionList)
		{
			Application.Trace(a.toString());
		}
	}
	/**
	 * If a object is listening to colour changing events, but wants to end his subscription,
	 * he can call this function with his identity.
	 * @param listener the listener which is removed from the list to whom notifies are sent
	 */
	public void removeColourChangedEventListener(ColourChangedEventListener listener)
	{
		listenerList.remove(ColourChangedEventListener.class, listener);
	}
	public void removeModeChangedEventListener(ModeChangedEventListener listener)
	{
		listenerList.remove(ModeChangedEventListener.class, listener);
	}
	public void removeObjectAddedEventListener(ObjectAddedEventListener listener)
	{
		listenerList.remove(ObjectAddedEventListener.class, listener);
	}
	public void removeObjectMovedEventListener(ObjectMovedEventListener listener)
	{
		listenerList.remove(ObjectMovedEventListener.class, listener);
	}
	public void removeObjectResizedEventListener(ObjectResizedEventListener listener)
	{
		listenerList.remove(ObjectResizedEventListener.class, listener);
	}
	public void removeObjectRotatedEventListener(ObjectRotatedEventListener listener)
	{
		listenerList.remove(ObjectRotatedEventListener.class, listener);
	}
	public void removeShapeChangedEventListener(ShapeChangedEventListener listener)
	{
		listenerList.remove(ShapeChangedEventListener.class, listener);
	}

	/**
	 * Replays the action list. Currently displayed objects will be thrown away. Make sure you backup them in
	 * case you need them. At the end of the replay the display list will be filled as if the objects were created
	 * just like in the recording session.
	 * 
	 * This is a blocking function! And might take a while, depending on the length of the recording session.
	 */
	public void replay()
	{
		Application.Trace("+++ Replay: " + actionList.size() + " items");

		if(actionList.size() > 0)
		{
			displayList = new Vector<Shape>();

			lastTS = getLastTimeStamp();
			firstTS = getFirstTimeStamp();
			timeFrame = lastTS.getTime() - firstTS.getTime();
			Application.Trace("\t+++ Replay first item@" + firstTS.getTime() + ", second item@" + lastTS.getTime() + ". Total frame " + timeFrame);

			startTimer();
		}
	}

	/**
	 * For now reset members by hand...
	 */
	public void reset()
	{
		displayList = new Vector<Shape>();
		actionList = new Vector<Action>();
		currentlyDrawingObject = null;
		currentDrawingShape = Shape.Shapes.Rectangle;
		drawingCanvas = null;
		repaint();
	}

	/**
	 * If there are multiple shapes in the position (x,y) the one with the highest Z-index
	 * will be removed. In this way you cannot accidently removed move shapes. The shape
	 * removed is added to currentlyDrawingObject, such that it won't get lost.
	 * @param coordinate the x and y coordinate of the mouse press
	 */
	private void resizeLastObject(Point2D coordinate)
	{
		Shape curShape = null;

		ListIterator<Shape> litr = displayList.listIterator(displayList.size());
		while(litr.hasPrevious())
		{
			curShape = litr.previous();
			if(curShape != null && curShape.contains(coordinate))
			{
				displayList.remove(curShape);
				Application.Trace("+++ Resizing object: " + curShape.toString());
				currentlyDrawingObject = curShape;
				return;
			}
		}

		/*
		 * Apparently a proper shape hasn't been found. That means we have nothing to resize
		 */
		if(litr.previousIndex() == -1 && currentlyDrawingObject != null && !currentlyDrawingObject.contains(coordinate)) {
			currentlyDrawingObject = null;
		}
	}

        /**
	 * If there are multiple shapes in the position (x,y) the one with the highest Z-index
	 * will be removed. In this way you cannot accidently removed move shapes. The shape
	 * removed is added to currentlyDrawingObject, such that it won't get lost.
	 * @param coordinate the x and y coordinate of the mouse press
	 */
	private void rotateLastObject(Point2D coordinate)
	{
		Shape curShape = null;

		ListIterator<Shape> litr = displayList.listIterator(displayList.size());
		while(litr.hasPrevious())
		{
			curShape = litr.previous();
			if(curShape != null && curShape.contains(coordinate))
			{
				//displayList.remove(curShape);
				Application.Trace("+++ Rotating object: " + curShape.toString());
				//currentlyDrawingObject = curShape;
				return;
			}
		}

		/*
		 * Apparently a proper shape hasn't been found. That means we have nothing to resize
		 */
		if(litr.previousIndex() == -1 && currentlyDrawingObject != null && !currentlyDrawingObject.contains(coordinate)) {
			currentlyDrawingObject = null;
		}
	}

	/**
	 * @param actionList
	 */
	public void setActionList(Vector<Action> actionList)
	{
		this.actionList = actionList;
		Application.Trace("+++ Action list imported");
	}
	public void setDelete()
	{
		Application.Trace("+++ Delete mode enabled");
		currentMode = Modes.Delete;
		fireModeChangedEvent(new ModeChangedEvent(this));

		if(currentlyDrawingObject != null)
		{
			displayList.add(currentlyDrawingObject);
			currentlyDrawingObject.setShowBoundingBox(false);
			currentlyDrawingObject = null;
		}
		repaint();
	}
	private void setDragging()
	{
		Application.Trace(" >  Dragging mode enabled");
		currentMode = Modes.Dragging;

		if(currentlyDrawingObject != null)
		{
			displayList.add(currentlyDrawingObject);
			currentlyDrawingObject.setShowBoundingBox(false);
			currentlyDrawingObject = null;
		}
		repaint();
	}

	/**
	 * Enable drawing mode
	 */
	public void setDraw()
	{
		Application.Trace("+++ Draw mode enabled");
		currentMode = Modes.Draw;
		fireModeChangedEvent(new ModeChangedEvent(this));

		if(currentlyDrawingObject != null)
		{
			displayList.add(currentlyDrawingObject);
			currentlyDrawingObject.setShowBoundingBox(false);
			currentlyDrawingObject = null;
		}
		repaint();
	}

	/**
	 * @param drawingCanvas the drawingCanvas to set
	 */
	public void setDrawingCanvas(JPanel drawingCanvas) {
		this.drawingCanvas = drawingCanvas;
	}

	/**
	 * Set the drawing mode to draw a triangle
	 */
	private void setDrawingTriangle()
	{
		Application.Trace("+++ DrawingTriangle mode enabled");
		currentMode = Modes.DrawingTriangle;
	}
	/**
	 * Enable filling mode
	 */
	public void setFill()
	{
		Application.Trace("+++ Fill mode enabled");
		currentMode = Modes.Fill;
		fireModeChangedEvent(new ModeChangedEvent(this));
		repaint();
	}
	/**
	 * @param fillColour the fillColour to set
	 */
	public void setFillColour(Color fillColour) {
		this.fillColour = fillColour;
		fireColourChangedEvent(new ColourChangedEvent(this));
	}
	/**
	 * @param lineColour the lineColour to set
	 */
	public void setLineColour(Color lineColour) {
		this.lineColour = lineColour;
		fireColourChangedEvent(new ColourChangedEvent(this));
	}
	/**
	 * @param lineWidth the lineWidth to set
	 */
	public void setLineWidth(int lineWidth) {
		this.lineWidth = lineWidth;
	}
	/**
	 * Enable moving mode
	 */
	public void setMove()
	{
		Application.Trace("+++ Move mode enabled");
		currentMode = Modes.Move;
		fireModeChangedEvent(new ModeChangedEvent(this));

		if(currentlyDrawingObject != null)
		{
			displayList.add(currentlyDrawingObject);
			currentlyDrawingObject.setShowBoundingBox(false);
			currentlyDrawingObject = null;
		}
		repaint();
	}
	/**
	 * Enable resizing mode
	 */
	public void setResize()
	{
		Application.Trace("+++ Resize mode enabled");
		currentMode = Modes.Resize;
		fireModeChangedEvent(new ModeChangedEvent(this));

		if(currentlyDrawingObject != null)
		{
			displayList.add(currentlyDrawingObject);
			currentlyDrawingObject.setShowBoundingBox(false);
			currentlyDrawingObject = null;
		}
		repaint();
	}
	/**
	 * Enable the internal resizing mode. This is purely internal, the user only knows
	 * the function setResize()
	 */
	protected void setResizing()
	{
		Application.Trace(" >  Resizing mode enabled");
		currentMode = Modes.Resizing;

		if(currentlyDrawingObject != null)
		{
			displayList.add(currentlyDrawingObject);
			currentlyDrawingObject.setShowBoundingBox(false);
			currentlyDrawingObject = null;
		}
		repaint();
	}
	/**
	 * Enable the rotating mode
	 */
	public void setRotate()
	{
		Application.Trace(" >  Rotate mode enabled");
		currentMode = Modes.Rotate;
		fireModeChangedEvent(new ModeChangedEvent(this));

		if(currentlyDrawingObject != null)
		{
			displayList.add(currentlyDrawingObject);
			currentlyDrawingObject.setShowBoundingBox(false);
			currentlyDrawingObject = null;
		}
		repaint();
	}
	/**
	 * Enable the internal rotating mode. This is purely internal, the user only knows
	 * the function setRotate()
	 */
	public void setRotating()
	{
		Application.Trace(" >  Rotating mode enabled");
		currentMode = Modes.Rotating;

		if(currentlyDrawingObject != null)
		{
			displayList.add(currentlyDrawingObject);
			currentlyDrawingObject.setShowBoundingBox(false);
			currentlyDrawingObject = null;
		}
		repaint();
	}

	/**
	 * @param shape the shape to set
	 */
	public void setShape(Shapes shape) {
		this.currentDrawingShape = shape;
		fireShapeChangedEvent(new ShapeChangedEvent(this));
	}
	/**
	 * Enable text mode
	 */
	public void setText()
	{
		Application.Trace("+++ Text mode enabled");
		currentMode = Modes.Text;
		fireModeChangedEvent(new ModeChangedEvent(this));
		repaint();
	}
	/**
	 * Enable topping mode
	 */
	public void setTop()
	{
		Application.Trace("+++ Top mode enabled");
		currentMode = Modes.Top;
		fireModeChangedEvent(new ModeChangedEvent(this));
		repaint();
	}

	/**
	 * Shows an object that's being dragged. Does not create an object, it just makes sure it stays in the UI
	 * @param position
	 */
	private void showDraggingObject(Point2D position)
	{
		/*
		 * You can't draw anything with width or height zero
		 */
		if(Math.abs(position.getX()-mousePressed.getX()) > 0 && Math.abs(position.getY()-mousePressed.getY()) > 0)
		{
			currentlyDrawingObject = Shape.createShape(currentDrawingShape,
					new Point2D.Double(mousePressed.getX(), mousePressed.getY()),
					position,
					lineColour, fillColour, lineWidth);

			repaint();
		}
	}

	/**
	 * Shows an objects that's being moved. Does not create the object, it just makes sure it stays in the UI
	 * @param position
	 */
	private void showMovingObject(Point2D position)
	{
		Shape s;
		Vector<Point2D> points = null;
		if(currentlyDrawingObject.getClass().equals(FreeLine.class))
		{
			FreeLine fl = (FreeLine) currentlyDrawingObject;
			points = fl.getPoints();
		}

		double xDif = currentlyDrawingObject.getStart().getX() - position.getX();
		double yDif = currentlyDrawingObject.getStart().getY() - position.getY();

		Point2D midCur = currentlyDrawingObject.getMiddle();
		s = currentlyDrawingObject;
		s.setMiddle(midCur);

		s.moveShape(xDif, yDif);
		currentlyDrawingObject = null;
		currentlyDrawingObject = s;

		if(currentlyDrawingObject.getClass().equals(FreeLine.class))
		{
			FreeLine fl = (FreeLine) currentlyDrawingObject;
			fl.setPoints(points);
			fl.movePoints(xDif, yDif);
		}

		repaint();
	}

	/**
	 * @param side
	 * @param position
	 */
	private void showResizingObject(Shape.Sides side, Point2D position)
	{
		if(resizeSide != null && projectedObjectPoint == null) {
			currentlyDrawingObject.resizeShape(resizeSide, position.getX()-mousePressed.getX(), position.getY()-mousePressed.getY());
		}

		if(resizeSide != null && projectedObjectPoint != null) {
			currentlyDrawingObject.resizeShape(resizeSide, position.getX()-projectedObjectPoint.getX(), position.getY()-projectedObjectPoint.getY());
		}

		projectedObjectPoint = position;
	}

	/**
	 * Start the replay timer. And stop it if the replay is over. As a side effect actions will be
	 * printed. This function depends on actionList being sorted, with monadic function <=
	 */
	void startTimer()
	{
		replayTimer = new Timer(replaySpeed, new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				currentTime += replaySpeed;
				if(currentTime >= timeFrame) {
					replayTimer.stop();
				}

				double timeDif = actionList.get(replayPointer).getTimeEnd().getTime() // the time of that event
				- firstTS.getTime() // now we have delta time, the relative time
				- currentTime; // to progress in time

				if(timeDif < replaySpeed)
				{
					if(actionList.size() > replayPointer + 1)
					{
						Application.Trace("\t>>> " + actionList.get(replayPointer).toString());
						// do something with the action
						replayPointer++;
					} else
					{
						Application.Trace("\t>>> " + actionList.get(replayPointer).toString());
						// do something with the action
						replayTimer.stop();
						percentage = 100;
						return;
					}
				}

				percentage = currentTime/timeFrame*100;
				Application.Trace("\t>>>" + percentage + "%");
			}
		});

		replayTimer.start();
	}

	/**
	 * If there are multiple shapes in the position (x,y) the one with the highest Z-index
	 * will be filled. In this way you cannot accidently fill hidden shapes.
	 * @param coordinate the x and y coordinate of the mouse press
	 */
	private void topLastObject(Point2D coordinate)
	{
		Shape curShape = null;

		ListIterator<Shape> litr = displayList.listIterator(displayList.size());
		while(litr.hasPrevious())
		{
			curShape = litr.previous();
			if(curShape != null && curShape.contains(coordinate))
			{
				displayList.remove(curShape);
				//				displayList.add(curShape);
				addAction(new Action(Action.Actions.TopObject, curShape, mousePressedTS, mouseReleasedTS));
				addShape(curShape);
				Application.Trace("+++ Topping object: " + curShape.toString());

				//				actionList.add(new Action(Action.Actions.TopObject, curShape, mousePressedTS, mouseReleasedTS));
				return;
			}
		}
	}
}
